//=================================================================
// 
// Renderer.cpp
// 
// Written by Luke Olsen, Jan. 2004
// Based on example code distributed with Qt under GPL license.
//
// Provides a bare-bones implementation of a GL widget under Qt.
// Code stubs are provided; have to add code to draw to the 
// screen using OpenGL API calls.
// 
//=================================================================

#include "renderer.h"
#include <GL/glut.h>
#include <iostream>
#include <math.h>
using namespace std;

//=================================================================
// Constructor: Initialize class variables, etc.
//=================================================================
Renderer::Renderer( QWidget* parent, const char* name )
    : QGLWidget( parent, name )
{
	// Initialize variables here
	cameraZ = 25.0;
	
	scale.x = scale.y = scale.z = 1;
	uscale.x = uscale.y = uscale.z = 1;
	
	radius = 1.0f;
	cycles = 5;
	
	showTangents = false;
	bDrawHelix = true;
	xRot = yRot = zRot = 0;
	
	// Set up the timer, connect it to the animation slot
	srand(time(NULL));
	timer = new QTimer( this );
	connect(timer, SIGNAL(timeout()), this, SLOT(animate()));
}

//=================================================================
// Destructor: Free memory, delete glLists, etc.
//=================================================================
Renderer::~Renderer()
{

}

//=================================================================
// PaintGL(): Draw to the screen using OpenGL API commands
//=================================================================
void Renderer::paintGL()
{
   glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(60, screenWidth/screenHeight, 15, 0);
	
	if (rollerCoaster.fixedView) {		
	gluLookAt(0.0, 10, 20, // Eye/camera position
	0.0, 0.0, 0.0,     // Look-at position
	0.0, 1.0, 0.0);    // "Up" vector
	}
	else {
	
		int uIndex = (int)(rollerCoaster.curve.currentU / rollerCoaster.curve.uStep);
		
		Point at = rollerCoaster.curve.getU(rollerCoaster.curve.currentU);
		Vector3 tangent = rollerCoaster.curve.tangent[uIndex];
		gluLookAt(at.x, at.y+0.5, at.z, // Eye/camera position
		at.x+tangent.x, at.y+tangent.y+0.5, at.z+tangent.z,     // Look-at position
		0.0, 1.0, 0.0);    // "Up" vector
	}
	
	glShadeModel(GL_SMOOTH);
		
	/*GLfloat pos[] = {0, 1, 1, 0};
	glLightf(GL_LIGHT0, GL_POSITION, *pos);
	//Need to enable lighting, a light, and the depth test
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	//Insert code for using glColor with lighting
	glColorMaterial(GL_FRONT_AND_BACK, GL_EMISSION);
	glEnable(GL_COLOR_MATERIAL);*/
	
	//glMatrixMode(GL_MODELVIEW);
	
		// set up the camera
		//glRotatef(xRot,1,0,0);
		//glRotatef(yRot,0,1,0);
		//glRotatef(zRot,0,0,1);
		
		//drawHelix();
	
	display();
	drawGroundPlane();
	
		
	//glDisable(GL_LIGHTING);
	//glDisable(GL_LIGHT0);
	
		
	glLoadIdentity();
    

    glPopMatrix();
    
    glFlush();
}

//=================================================================
// initializeGL(): Set up the OpenGL rendering state,
//    define lights, call glEnable()s, load textures, etc.
//=================================================================
void Renderer::initializeGL()
{     
    // Set up various other stuff
    glClearColor( 0.0, 0.0, 0.0, 0.0); // Let OpenGL clear to black
    GLfloat whiteDir[4] = {2.0, 2.0, 2.0, 1.0};
    GLfloat whiteAmb[4] = {1.0, 1.0, 1.0, 1.0};
    GLfloat lightPos[4] = {10.0, 60.0, 0.0, 1.0};

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, whiteAmb);

    glMaterialfv(GL_FRONT, GL_DIFFUSE, whiteDir);
    glMaterialfv(GL_FRONT, GL_SPECULAR, whiteDir);
    glMaterialf(GL_FRONT, GL_SHININESS, 30.0);

    glLightfv(GL_LIGHT0, GL_DIFFUSE, whiteDir);		// enable diffuse
    glLightfv(GL_LIGHT0, GL_SPECULAR, whiteDir);	// enable specular
    glLightfv(GL_LIGHT0, GL_POSITION, lightPos);

    glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
    glShadeModel( GL_SMOOTH );
    glEnable( GL_DEPTH_TEST );
    glEnable( GL_COLOR_MATERIAL );

    // Set up texturing
    glEnable( GL_TEXTURE_2D );
    glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    
    // initialize matrix to identity
	for (int i=0; i<4; i++)
		for (int j=0; j<4; j++)
			if (i==j)
				matrix[i*4 + j] = 1;
			else
				matrix[i*4 + j] = 0;
}

//=================================================================
// resizeGL(): Set up the OpenGL viewport (frustum or ortho)
//=================================================================
void Renderer::resizeGL( int w, int h )
{
	screenWidth = w;
	screenHeight = h;
	
    glViewport( 0, 0, w, h );
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();

    if(w>=h){
      double fc = w/(double)h;
      glFrustum( -1.0*fc, 1.0*fc, -1.0, 1.0, 2.0, 1000.0 );
    }
    else if(h>w){
      double fc = h/(double)w;
      glFrustum( -1.0, 1.0, -1.0*fc, 1.0*fc, 2.0, 1000.0 );
    }

    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();
}

//=================================================================
// animate(): Perform animation-related updates whenever the 
//            timer event fires
//=================================================================
void Renderer::animate()
{
	// Update animation-related variables

	// Request display redraw
	updateGL();
}


//=================================================================
// Intercept user double-clicks in the GL window area
//=================================================================
void Renderer::mouseDoubleClickEvent(QMouseEvent *e)
{
}

//=================================================================
// Intercept user mouse presses in the GL window
//=================================================================
void Renderer::mousePressEvent( QMouseEvent *e )
{
  mousePressed = TRUE;
  oldx = e->x();
  oldy = e->y();
  button = e->button();
  
  cout << "x: " << e->x() << endl;
  cout << "y: " << e->y() << endl << endl;
}

//=================================================================
// Intercept user mouse releases in the GL window
//=================================================================
void Renderer::mouseReleaseEvent( QMouseEvent *e )
{
  mousePressed = FALSE;
  oldx = e->x();
  oldy = e->y();
}

//=================================================================
// Intercept user mouse movements (while holding a mouse button)
//=================================================================
void Renderer::mouseMoveEvent( QMouseEvent *e )
{
  if(mousePressed){

    double xmov;
    double ymov;
    
    xmov = -e->x() + oldx;
    ymov = -oldy + e->y();

    if (button == LeftButton) {
    }
    else if (button == MidButton) {
    }
    else if (button == RightButton) {
    }    
  }

  oldx = e->x();
  oldy = e->y();

  updateGL();
}

void Renderer::keyPressEvent(QKeyEvent *e) {
	float speed = 3.3f; //The speed of rotation
	radius = 5;

	switch (e->key()) {
		case Qt::Key_Q:
			radius = 5;
			zRot -= speed;
			break;
		case Qt::Key_W:
			xRot += speed;
			break;
	}
}

//=================================================================
// Scaling
//=================================================================

/********************************************************************************************
*xScale
*	To be called by the user interface.  Scales the mesh by 1/10 of the specified value.
*Parameters:
*	int value	-> The amount to scale
*Returns:
*	none
*********************************************************************************************/
void Renderer::xScale(int value) {
	scale.x = (float)value/10;
	updateGL();
}

/********************************************************************************************
*yScale
*	To be called by the user interface.  Scales the mesh by 1/10 of the specified value.
*Parameters:
*	int value	-> The amount to scale
*Returns:
*	none
*********************************************************************************************/
void Renderer::yScale(int value) {
	scale.y = (float)value/10;
	updateGL();
}

/********************************************************************************************
*zScale
*	To be called by the user interface.  Scales the mesh by 1/10 of the specified value.
*Parameters:
*	int value	-> The amount to scale
*Returns:
*	none
*********************************************************************************************/
void Renderer::zScale(int value) {
	scale.z = (float)value/10;
	updateGL();
}

/********************************************************************************************
*uniformScale
*	To be called by the user interface.  Scales the mesh by 1/10 of the specified value.
*Parameters:
*	int value	-> The amount to scale
*Returns:
*	none
*********************************************************************************************/
void Renderer::uniformScale(int value) {
	uscale.x = uscale.y = uscale.z = (float)value/10;
	updateGL();
}

void Renderer::xView(int value) {
	//xRot = ((float)value*3.14)/180;
	xRot = value;
	updateGL();
}

void Renderer::yView(int value) {
	yRot = value;
	updateGL();
}

void Renderer::zView(int value) {
	zRot = value;
	updateGL();
}

/********************************************************************************************
*drawMesh
*	The window resizing function used by QT.  Sets the windows width and height to
*	width and height.
*Parameters:
*	int w	-> The width of the window
*	int h	-> The height of the window
*Returns:
*	none
*********************************************************************************************/
void Renderer::drawHelix() {
	float u = 0.0f;
	
	glBegin(GL_LINE_STRIP);
	glColor3f(1.0, 0.0, 0.0);
	
	float pitch = 0.2f;
	
	while (u < (2*PI*cycles)) {
		glVertex3f(radius*cos(u), radius*sin(u), pitch*u);
		u = u+0.01;
	}
	glEnd();
	
	if (showTangents) {
		//tangent = (-radius*sin(u), radius*cos(u), pitch*u)
		glBegin(GL_LINES);
		glColor3f(0.0, 1.0, 0.0);
		
		u = 0.0f;
		float pitch = 0.2f;
		
		while (u < (2*PI*cycles)) {
			Vector3 v1, v2, tangent, result1, result2;
			
			v1 = Vector3(radius*cos(u), radius*sin(u), pitch*u);
			v2 = Vector3(radius*cos(u+0.01), radius*sin(u+0.01), pitch*(u+0.01));
			
			tangent = v2-v1;
			
			result1 = v1+(tangent*30);
			result2 = v1-(tangent*30);
			
			glVertex3f(result1.x, result1.y, result1.z);
			glVertex3f(result2.x, result2.y, result2.z);
			u = u+1;
		}
		glEnd();
	}
}

void Renderer::drawDNA() {
	float u = 0.0f;
	
	glBegin(GL_LINE_STRIP);
	glColor3f(1.0, 0.0, 0.0);
	
	float pitch = 0.2f;
	float radius2 = 0.5f;
	float cycles2 = 10;
	
	while (u < (2*PI*cycles2)) {
		glVertex3f(radius2*cos(u), radius2*sin(u), ((pitch*u)-10)*4.5);
		u = u+0.01;
	}
	glEnd();
	
	glBegin(GL_LINE_STRIP);
	glColor3f(1.0, 0.0, 0.0);
	
	u = 0.0f;
	
	while (u < (2*PI*cycles2)) {
		glVertex3f(-radius2*cos(u), -radius2*sin(u), ((pitch*u)-10)*4.5);
		u = u+0.01;
	}
	glEnd();
	
	u = 0.0f;
	
	
	float x, y, z;
	x = y = z = 0.0f;
	glBegin(GL_LINES);
	while (u < (2*PI*cycles2)) {
		//colour changing pattern
		x += 0.3;
		if (x > 1) x = 0;
		y += 0.7;
		if (y > 1) y = 0;
		z += 0.1;
		if (z > 1) z = 0;
		if (x==y==z==0) z = 1;
		
		glColor3f(x, y, z);
	
		glVertex3f(radius2*cos(u), radius2*sin(u), ((pitch*u)-10)*4.5);
		glVertex3f(-radius2*cos(u), -radius2*sin(u), ((pitch*u)-10)*4.5);
		u = u+0.07f;
	}
	glEnd();
	
	
}

void Renderer::radiusTextChanged(const QString &text) {
	bool ok;
	float val = text.toFloat(&ok);
	if (ok) {
		radius = val;
	}
	/*if (text == "") {
		lookAt.x = 0;
	}*/
	updateGL();
}

void Renderer::viewTextChanged(const QString &text) {
	bool ok;
	float val = text.toFloat(&ok);
	if (ok) {
		scale.y = val;
	}
	/*if (text == "") {
		lookAt.x = 0;
	}*/
	updateGL();
}

void Renderer::tangentChanged(bool b) {
	showTangents = b;
	updateGL();
}

void Renderer::cyclesChanged(int i) {
	cycles = i;
	updateGL();
}

void Renderer::play() {
	rollerCoaster.restart();
	createTimer();
	
	updateGL();
}

void Renderer::changeView() {
	rollerCoaster.fixedView = !rollerCoaster.fixedView;
}

void Renderer::debug() {
	rollerCoaster.debug = !rollerCoaster.debug;
}

//=================================================================
//=================================================================
//=================================================================
//=================================================================
//=================================================================
//=================================================================

/*screenConvert
*	Takes a given screen coordinate, and maps it into the range -1 <= x <= 1 and -1 <= y <= 1
*	This code was given by the TA, Sean.
*Parameters:
*	float x	-> The x coordinate
*	float y	-> The y coordinate
*Returns:
*	p	-> The vector value of the new point
*/
Vector3 Renderer::screenConvert(float x, float y) {
	//You need to get your coordinates to range within -1, 1 in both x and y directions
	//but you need to check if the ratio is different, and compute accordingly
	float sWidth2 = (float)screenWidth/2.0f;
	float sHeight2 = (float)screenHeight/2.0f;
	x -= sWidth2;
	x /= sWidth2;
	y -= sHeight2;
	y /= sHeight2;
	y*=-1.0f;
	float ratio;
    if ( screenWidth > screenHeight ) // In this case the w/h ratio is < 1
	{
		ratio = (float)screenWidth/(float)screenHeight;
		x*= ratio;
	}
	else        // In this case the h/w ratio is > 1
	{
		ratio = (float)screenHeight/(float)screenWidth;
		y*= ratio;
	 }
	Vector3 p = Vector3(x,y);
	return p;
}

/*display
*	Refreshes the drawing
*Parameters:
*	none
*Returns:
*	none
*/
void Renderer::display() {

	
	//gluLookAt(0, 0, 20, 0, 0, 0, 0, 1, 0);
	/*gluLookAt(0.0, 10.0, 20.0, // Eye/camera position
			0.0, -5.0, 0.0,     // Look-at position  
			0.0, 0.0, -1.0);    // "Up" vector*/
			

	
	//Need to enable lighting, a light, and the depth test
	//glEnable(GL_LIGHTING);
	//glEnable(GL_LIGHT0);
	//glEnable(GL_DEPTH_TEST);
	//Insert code for using glColor with lighting
	//glColorMaterial(GL_FRONT_AND_BACK, GL_EMISSION);
	//glEnable(GL_COLOR_MATERIAL);

	// set up the camera
	/*glRotatef(xRot,1,0,0);
	glRotatef(yRot,0,1,0);
	glRotatef(zRot,0,0,1);*/

	rollerCoaster.refresh();

}

void Renderer::drawGroundPlane() {
	float size = 100;
	Point p1, p2, p3, p4;
	
	p1 = Point(size/2, 0, -size/2);
	p2 = Point(-size/2, 0, -size/2);
	p3 = Point(-size/2, 0, size/2);
	p4 = Point(size/2, 0, size/2);

	glBegin(GL_TRIANGLES);
	glColor3f(0.1, 0.1, 0.1);
	glVertex3f(p1.x, p1.y, p1.z);
	glVertex3f(p2.x, p2.y, p2.z);
	glVertex3f(p3.x, p3.y, p3.z);
	glEnd();
	
	glBegin(GL_TRIANGLES);
	glColor3f(0.1, 0.1, 0.1);
	glVertex3f(p1.x, p1.y, p1.z);
	glVertex3f(p3.x, p3.y, p3.z);
	glVertex3f(p4.x, p4.y, p4.z);
	glEnd();
}

void Renderer::loadCurve() {
	QString s = QFileDialog::getOpenFileName("./", "All (*.txt)");
	
	string s2 = s.utf8().data();
	
	rollerCoaster.curve.read(s2);
	
	updateGL();
}

/********************************************************************************************
*createTimer
*	Creates a QTimer.  The timeout value of the timer is based on the number of frames,
*	as well as the fps of the current animation.
*Parameters:
*	none
*Returns:
*	none
*********************************************************************************************/
void Renderer::createTimer() {	
	//set update to happen 50 times/second (every 20 milliseconds)
	updateGL();
	//start the timer
	QTimer *timer = new QTimer(this);
	connect(timer, SIGNAL(timeout()), this, SLOT(timerDone()));
	timer->start(30, TRUE);

}

/********************************************************************************************
*timerDone
*	Executes when the timeout signal is given by the QTimer created in createTimer().
*	It increments the frame, and then creates a new timer.
*Parameters:
*	none
*Returns:
*	none
*********************************************************************************************/
void Renderer::timerDone() {
	rollerCoaster.update(30);
	createTimer();
}